# util/_immutabledict_cy.py
# Copyright (C) 2010-2025 the SQLAlchemy authors and contributors
# <see AUTHORS file>
#
# This module is part of SQLAlchemy and is released under
# the MIT License: https://www.opensource.org/licenses/mit-license.php
# mypy: disable-error-code="misc, arg-type"
from __future__ import annotations

from typing import Any
from typing import Dict
from typing import Hashable
from typing import Mapping
from typing import NoReturn
from typing import Optional
from typing import TypeVar

from .typing import Self

# START GENERATED CYTHON IMPORT
# This section is automatically generated by the script tools/cython_imports.py
try:
    # NOTE: the cython compiler needs this "import cython" in the file, it
    # can't be only "from sqlalchemy.util import cython" with the fallback
    # in that module
    import cython
except ModuleNotFoundError:
    from sqlalchemy.util import cython


def _is_compiled() -> bool:
    """Utility function to indicate if this module is compiled or not."""
    return cython.compiled  # type: ignore[no-any-return,unused-ignore]


# END GENERATED CYTHON IMPORT

if cython.compiled:
    from cython.cimports.cpython.dict import PyDict_Update
else:
    PyDict_Update = dict.update


def _immutable_fn(obj: object) -> NoReturn:
    raise TypeError(f"{obj.__class__.__name__} object is immutable")


class ReadOnlyContainer:
    __slots__ = ()

    def _readonly(self) -> NoReturn:
        raise TypeError(
            f"{self.__class__.__name__} object is immutable and/or readonly"
        )

    def __delitem__(self, key: Any) -> NoReturn:
        self._readonly()

    def __setitem__(self, key: Any, value: Any) -> NoReturn:
        self._readonly()

    def __setattr__(self, key: Any, value: Any) -> NoReturn:
        self._readonly()


_KT = TypeVar("_KT", bound=Hashable)
_VT = TypeVar("_VT", bound=Any)


@cython.cclass
class ImmutableDictBase(Dict[_KT, _VT]):
    # NOTE: this method is required in 3.9 and speeds up the use case
    # ImmutableDictBase[str,int](a_dict) significantly
    @classmethod
    def __class_getitem__(  # type: ignore[override]
        cls, key: Any
    ) -> type[Self]:
        return cls

    def __delitem__(self, key: Any) -> NoReturn:
        _immutable_fn(self)

    def __setitem__(self, key: Any, value: Any) -> NoReturn:
        _immutable_fn(self)

    def __setattr__(self, key: Any, value: Any) -> NoReturn:
        _immutable_fn(self)

    def clear(self) -> NoReturn:
        _immutable_fn(self)

    def pop(self, key: Any, default: Optional[Any] = None) -> NoReturn:
        _immutable_fn(self)

    def popitem(self) -> NoReturn:
        _immutable_fn(self)

    def setdefault(self, key: Any, default: Optional[Any] = None) -> NoReturn:
        _immutable_fn(self)

    def update(self, *arg: Any, **kw: Any) -> NoReturn:
        _immutable_fn(self)


# NOTE: can't extend from ImmutableDictBase[_KT, _VT] due to a compiler
# crash in doing so. Extending from ImmutableDictBase is ok, but requires
# a type checking section and other workaround for the crash
@cython.cclass
class immutabledict(Dict[_KT, _VT]):
    """An immutable version of a dict."""

    # ImmutableDictBase start
    @classmethod
    def __class_getitem__(  # type: ignore[override]
        cls, key: Any
    ) -> type[Self]:
        return cls

    def __delitem__(self, key: Any) -> NoReturn:
        _immutable_fn(self)

    def __setitem__(self, key: Any, value: Any) -> NoReturn:
        _immutable_fn(self)

    def __setattr__(self, key: Any, value: Any) -> NoReturn:
        _immutable_fn(self)

    def clear(self) -> NoReturn:
        _immutable_fn(self)

    def pop(self, key: Any, default: Optional[Any] = None) -> NoReturn:
        _immutable_fn(self)

    def popitem(self) -> NoReturn:
        _immutable_fn(self)

    def setdefault(self, key: Any, default: Optional[Any] = None) -> NoReturn:
        _immutable_fn(self)

    def update(self, *arg: Any, **kw: Any) -> NoReturn:
        _immutable_fn(self)

    # ImmutableDictBase end

    def __repr__(self) -> str:
        return f"immutabledict({dict.__repr__(self)})"

    @cython.annotation_typing(False)  # avoid cython crash from generic return
    def union(
        self, other: Optional[Mapping[_KT, _VT]] = None, /
    ) -> immutabledict[_KT, _VT]:
        if not other:
            return self
        # new + update is faster than immutabledict(self)
        result: immutabledict = immutabledict()  # type: ignore[type-arg]
        PyDict_Update(result, self)
        if isinstance(other, dict):
            # c version of PyDict_Update supports only dicts
            PyDict_Update(result, other)
        else:
            dict.update(result, other)
        return result

    @cython.annotation_typing(False)  # avoid cython crash from generic return
    def merge_with(
        self, *dicts: Optional[Mapping[_KT, _VT]]
    ) -> immutabledict[_KT, _VT]:
        result: Optional[immutabledict] = None  # type: ignore[type-arg]
        d: object
        if not dicts:
            return self
        for d in dicts:
            if d is not None and len(d) > 0:
                if result is None:
                    # new + update is faster than immutabledict(self)
                    result = immutabledict()
                    PyDict_Update(result, self)
                if isinstance(d, dict):
                    # c version of PyDict_Update supports only dicts
                    PyDict_Update(result, d)
                else:
                    dict.update(result, d)

        return self if result is None else result

    def copy(self) -> Self:
        return self

    def __reduce__(self) -> Any:
        return immutabledict, (dict(self),)

    # PEP 584
    def __ior__(self, __value: Any, /) -> NoReturn:
        _immutable_fn(self)

    def __or__(  # type: ignore[override]
        self, __value: Mapping[_KT, _VT], /
    ) -> immutabledict[_KT, _VT]:
        return immutabledict(
            dict.__or__(self, __value),  # type: ignore[call-overload]
        )

    def __ror__(  # type: ignore[override]
        self, __value: Mapping[_KT, _VT], /
    ) -> immutabledict[_KT, _VT]:
        return immutabledict(
            dict.__ror__(self, __value),  # type: ignore[call-overload]
        )
